/*
    Contact info:
    bhepple@freeshell.org
    http://bhepple.freeshell.org/scrmgr

    Copyright (C) 1991-2008 Bob Hepple

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
    Boston, MA 02111-1307, USA.

*/
#include <getopt.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>	/* For import_export */
#include <setjmp.h>
#include "smlib.h"
#include "smio.h"
#include "backup.h"

#include "smcreate.def"

#define NO_REPAINT	0
#define REPAINT_LINE	1
#define REPAINT_ALL	2

CHAR *smcreate_rcsid(void) {
    return("$Id: smcreate.c,v 1.7 2008/02/19 13:15:50 bhepple Exp $");
}

static CHAR	rev_string[] = "$Revision: 1.7 $";
int 		dirty_data = 0;
static CHAR	last_filename[MAXPATHLEN + 1] = "";
static CHAR	last_direct  [MAXPATHLEN + 1] = "./";
jmp_buf		NoMoreHeap;
#define CURRENT_SCR	screen_edt->current_scr
static CHAR *usage_msg[] = {
"Editor for SCRMGR screen files",
"",
"screenfile:    a SCRMGR screen file to edit",
"-d screen :    dump 'screen' to stdout",
"-k softkeys:   dump 'softkeys' to stdout",
NULL
};

struct {
    CHAR	*screen_dump;
    CHAR	*skey_dump;
} global = {
    NULL,
    NULL
};

static void usage()
{
    CHAR **c;
    
    fprintf(stderr, "Revision %s of %s.\n", rev_string, sm_program_name);
    fprintf(stderr, "Usage: %s [ [ -d screen ] [-k softkeys] screenfile ]\n", sm_program_name);
    
    c = usage_msg;
    while (*c) {
        fputs(*c, stderr);
        fputc('\n', stderr);
        c++;
    }
    exit(2);
}

void sm_error_exit ARG1(CHAR *, msg)
{
    sm_term();
    puts(msg);
    exit(1);
}

/* Stubs to be overidden if needed (at the moment, only if your application
   uses field triggers) */

static COMMAND month_trigger ARG3(SM_FIELD, 	field, 
				  COMMAND, 	command, 
				  int, 		window)
{
    SM_SCREEN	scr;
    SM_FIELD	fld;

    assert(field);

    scr = field->parent;
    /* If this field goes to 'Y' then set numeric to 'N' */
    if (*sm_fld_enq(field) == 'Y') {
	fld = sm_fld_open(scr, "numeric");
	if (fld) {
	    sm_fld_set(fld, "N");
	    sm_paint_fld(window, fld->c_value, fld, 1, 0);
	}
	/* reset the cursor in the right place etc */
	sm_paint_fld(window, field->c_value, field, 1, 0);
    }
    return(command);
}

static COMMAND numeric_trigger ARG3(SM_FIELD, 	field, 
				    COMMAND, 	command, 
				    int, 	window)
{
    SM_SCREEN	scr;
    SM_FIELD	fld;

    assert(field);

    scr = field->parent;
    /* If this field goes to 'Y' then set month to 'N' */
    if (*sm_fld_enq(field) == 'Y') {
	fld = sm_fld_open(scr, "month");
	if (fld) {
	    sm_fld_set(fld, "N");
	    sm_paint_fld(window, fld->c_value, fld, 1, 0);
	}
	/* reset the cursor in the right place etc */
	sm_paint_fld(window, field->c_value, field, 1, 0);
    }
    return(command);
}

/* Exported to the SCRMGR package */
f_ptr sm_get_field_trigger ARG1(SM_FIELD, field)
{
    assert(field);

    if (     strcmp(field->trigger_fn, "month_trigger") == 0)
	return(month_trigger);
    if (     strcmp(field->trigger_fn, "numeric_trigger") == 0)
	return(numeric_trigger);
    return(NULL);
}

#if !READ_DAT_AT_RUNTIME
static CHAR *smcreate_dat[] =
{
#include "smcreate.dat"
NULL
};
#endif

static void sm_del_char ARG3(SM_SCREEN, screen, int, row, int, col)
{
    SM_BACKGROUND	bg;
    SM_FIELD		*fieldp;
    
    assert(screen);

    /* Deal with fields */
    fieldp = sm_first_fld(screen);
    while (fieldp && *fieldp) {
        if (((*fieldp)->row == row) && ((*fieldp)->col > col))
            ((*fieldp)->col)--;
        fieldp = sm_next_fld(fieldp);
    }
    
    /* Deal with bg */
    if ((bg = sm_pick_bg(screen, row)))
	delCharInString(bg->text, col);
}

static int sm_add_char 
ARG5(SM_SCREEN, screen, int, row, int, col, CHAR, newchar, int, attribute)
{
    SM_FIELD		*fieldp;
    SM_BACKGROUND	bg;
    int			maxcol;
    int			maxrow;
    
    assert(screen);

    /* Check that no field gets bumped off the right in insert mode */
    if (smio_insertp()) {
        fieldp = sm_first_fld(screen);
        sm_enq_term(-1, &maxrow, &maxcol);
        while (fieldp && *fieldp) {
            if (((*fieldp)->row == row) && 
                ((*fieldp)->col + abs((*fieldp)->len) >= maxcol))
                return(-1);
            fieldp = sm_next_fld(fieldp);
        }
	
        /* Move fields over one place */
        fieldp = sm_first_fld(screen);
        while (fieldp && *fieldp) {
            if (((*fieldp)->row == row) &&
                ((*fieldp)->col >= col))
                ((*fieldp)->col)++;
            fieldp = sm_next_fld(fieldp);
        }
    }
    
    /* Get a bg component on this row */
    bg = sm_pick_bg(screen, row);
    
    if (bg == NULL) {
        bg = sm_create_bg(screen);
        bg->row = row;
    }

    if (bg->text == NULL) {
        bg->text = newString(col);
    }
    
    if (smio_insertp())
	insCharInString(bg->text, col, newchar);
    else
	setCharInString(bg->text, col, newchar);

    return(0);
}

/* Should only be called if the rest of this line is blank */
static int sm_join_lines ARG3(SM_SCREEN, screen, int, row, int, col)
{
    int			maxcol, maxrow, newcol, rollrow;
    SM_FIELD		*fieldp;
    SM_BACKGROUND	thisbg, nextbg, *bgp;
    
    assert(screen);

    sm_enq_term(-1, &maxrow, &maxcol);
    
    /* Deal with fields - only move entire fields */
    fieldp = sm_first_fld(screen);
    while (fieldp && *fieldp) {
        if ((*fieldp)->row == row + 1) {
            newcol = col + (*fieldp)->col;
            if (newcol + abs((*fieldp)->len) <= maxcol) {
                (*fieldp)->row = row;
                (*fieldp)->col = newcol;
            }
        } else if ((*fieldp)->row > row + 1)
            ((*fieldp)->row)--;
        fieldp = sm_next_fld(fieldp);
    }
    
    /* Deal with background */
    rollrow = row + 1;
    thisbg = sm_pick_bg(screen, row);
    nextbg = sm_pick_bg(screen, row + 1);
    if (thisbg && nextbg) {
	rollrow++;
	if (col >= StringLength(thisbg->text)) {
	    setCharInString(thisbg->text, col, ' '); /* extends the string */
	    delCharInString(thisbg->text, -1);
	}
	StringCat(thisbg->text, nextbg->text);
    }

    bgp = sm_first_bg(screen); 
    while (bgp && *bgp) {
	if ((*bgp)->row >= rollrow)
	    ((*bgp)->row)--;
	else if (thisbg && nextbg && (*bgp == nextbg)) {
	    bgp = sm_destroy_bg(bgp);
	    continue;
	}
	bgp = sm_next_bg(bgp);
    }
    return(0);
}

/* Clear the rest of the line. */
static int sm_clear_line ARG3(SM_SCREEN, screen, int, row, int, col)
{
    int			something_deleted = 0;
    SM_FIELD		*fieldp;
    SM_BACKGROUND	bg;

    if (screen == NULL)
	return(something_deleted);

    /* Deal with fields */
    fieldp = sm_first_fld(screen);
    while (fieldp && *fieldp) {
        if (((*fieldp)->row == row) && ((*fieldp)->col >= col)) {
            fieldp = sm_destroy_fld(fieldp);
            something_deleted = 1;
        } else
	    fieldp = sm_next_fld(fieldp);
    }
    
    /* Deal with bg */
    if (((bg = sm_pick_bg(screen, row)) != NULL) &&
	(col < StringLength(bg->text))) {
	truncString(bg->text, col);
	something_deleted = 1;
    }
    
    return(something_deleted);
}

static int sm_split_line ARG3(SM_SCREEN, screen, int, row, int, col)
{
    int			maxcol, maxrow;
    SM_BACKGROUND	bg, *bgp, new_bg = NULL;
    SM_FIELD		*fieldp, field;
    
    assert(screen);

    /* Check we are not in a field - inserting at the start of the
       field is OK */
    fieldp = sm_pick_fld(screen, row, col);
    if (fieldp && *fieldp) {
	field = *fieldp;
        if (!smio_insertp() || (col != field->col))
            return(-1);
    }
    
    sm_enq_term(-1, &maxrow, &maxcol);
    if (row > maxrow - 1)
        return(-1);
    
    /* Deal with fields */
    fieldp = sm_first_fld(screen);
    while (fieldp && *fieldp) {
	field = *fieldp;
        if (field->row > row)
            (field->row)++;
        else if (field->row == row) {
            if (field->col >= col) {
                (field->row)++;
                field->col -= col;
            }	
        }
        fieldp = sm_next_fld(fieldp);
    }
    
    /* Deal with bg */
    for (bgp = sm_first_bg(screen); bgp && *bgp; bgp = sm_next_bg(bgp))
        if ((*bgp)->row > row)
            ((*bgp)->row)++;

    if ((bg = sm_pick_bg(screen, row))) {
	if (col == 0)
	    (bg->row)++;
	else if (col < StringLength(bg->text)) {
	    new_bg = sm_create_bg(screen);
	    new_bg->row = row + 1;
	    new_bg->text = subString(bg->text, col, -1);
	    truncString(bg->text, col);
        }
    }
    
    return(0);
}

static int scr_name_lambda ARG2(int, command, SM_SCREEN, screen)
{
    switch (command) {
    default:
        return(command);
    }
}

struct scr_name_s {
    f_ptr	destructor;
    SM_SCREEN	this_screen;
};

static int scr_name_destructor ARG1(struct scr_name_s *, object)
{
    free(object);
    return(0);
}

CALLBACK(finish_scr_name_edt)
{
    struct scr_name_s *scr_name = (struct scr_name_s *) callback_data;
    
    alloca(0);

    if (command != SM_QUIT_C) {
	if (smdb_file_changed(screen_record)) {
	    CHAR	*buf;

	    /* delete old */
	    sm_destroy_all_sortkeys(scr_name->this_screen);

	    dirty_data = 1;
	    buf = smdb_seek(screen_record, (INDEX) 0);
	    while (buf && *buf) {
		/* ... store fields in structures */
		sm_read_scr(sm_user_screens(), 
			    scr_name->this_screen, 
			    buf);
		buf = smdb_read_next(screen_record);
	    }
	}
	if (command == PREV_KEYS)
	    command = SM_POPCALLBACK;
    } else
	command = SM_POPCALLBACK;

    /* Tear down data ... */
    smdb_delete_data(screen_record);
    return(command);
}

static void setup_scr_name_edt ARG1(SM_SCREEN, this_screen)
{
    CHAR		*buf;
    struct scr_name_s	*scr_name;
    SM_SORTKEY		*sortkeyp;
    SM_FIELD		field;
    
    assert(this_screen);

    buf = alloca(SM_MAXREC);
    scr_name = (struct scr_name_s *)xmalloc((INDEX) sizeof(struct scr_name_s));
    scr_name->destructor = scr_name_destructor;
    scr_name->this_screen = this_screen;

    /* Create a SM_DATA structure based on the screen name values */
    if ((sortkeyp = sm_first_sortkey(scr_name->this_screen)) && *sortkeyp)
	for (; 
	     sortkeyp && *sortkeyp; 
	     sortkeyp = sm_next_sortkey(sortkeyp)) {
	    sm_format_scr(scr_name->this_screen, buf, *sortkeyp);
	    smdb_write(screen_record, buf);
	}
    else {
	sm_format_scr(scr_name->this_screen, buf, NULL);
	smdb_write(screen_record, buf);
    }

    field = sm_fld_open(screen_record, "softkeys");
    if (field) {
	SM_SOFTKEY	*skeyp;

	sm_delete_field_popups(field);
	for (skeyp = sm_first_skey(this_screen->parent);
	     skeyp && *skeyp;
	     skeyp = sm_next_skey(skeyp))
	    sm_add_field_popup_item(field, (*skeyp)->skey_name);
	for (skeyp = sm_first_skey(sm_builtin_screens());
	     skeyp && *skeyp;
	     skeyp = sm_next_skey(skeyp))
	    sm_add_field_popup_item(field, (*skeyp)->skey_name);
    }

    smdb_seek(screen_record, (INDEX) 0);
    smdb_set_file_unchanged(screen_record);
    sm_add_callback(finish_scr_name_edt, scr_name);
    if (sm_setup_edit(SM_BASE_WINDOW,
		      screen_record, 
		      PREV_KEYS, 
		      scr_name_lambda,
		      scr_name_skey,
		      this_screen) == SM_ERROR)
	sm_destroy_callback();
}

struct field_edt_s {
    f_ptr	destructor;
    SM_SCREEN	screen;
    SM_FIELD	field;
};

static int field_edt_destructor ARG1(struct field_edt_s *, object)
{
    free(object);
    return(0);
}

CALLBACK(finish_field_edt)
{
    struct field_edt_s 	*field_edt = (struct field_edt_s *) callback_data;
    CHAR		**temp;

    alloca(0);

    if (command != SM_QUIT_C) {
	if (smdb_file_changed(field_record)) {
	    dirty_data = 1;
	
	    /* Out with the old ... */
	    sm_destroy_all_flds(field_edt->screen);
	
	    /* In with the new ... store fields in structures */
	    for (temp = (CHAR **)firstArrayItemp(field_record->data);
		 temp && *temp; 
		 temp = (CHAR **)nextArrayItemp(field_record->data, (void **)temp))
		sm_read_fld(field_edt->screen, NULL, *temp);

	    sm_check_data_field(field_edt->screen);	    
	}
	if (command == PREV_KEYS)
	    command = SM_POPCALLBACK;
    } else
	command = SM_POPCALLBACK;
	

    /* Tear down data ... */
    smdb_delete_data(field_record);

    return(command);
}

static void setup_field_edt ARG2(SM_SCREEN, screen, SM_FIELD, this_field)
{
    CHAR		*buf;
    SM_FIELD		*f;
    SM_SCREEN		*screenp;
    struct field_edt_s	*field_edt;
    INDEX		index = 0;
    
    assert(screen && this_field);

    buf = alloca(SM_MAXREC);
    field_edt = (struct field_edt_s *)xmalloc((INDEX) sizeof(struct field_edt_s));
    field_edt->destructor = field_edt_destructor;
    field_edt->screen = screen;
    field_edt->field = this_field;

    /* Create a SM_DATA structure based on the field values */
    for (f = sm_first_fld(screen); f && *f; f = sm_next_fld(f)) {
        if (*f == this_field)
            index = smdb_enq_position(field_record);
        sm_format_fld(*f, buf);
        smdb_write(field_record, buf);
    }

    /* Create popups on the 'key' field of field_record */
    sm_delete_field_popups(sm_fld_open(field_record, "key"));
    for (screenp = sm_first_scr(screen->parent);
	 screenp && *screenp;
	 screenp = sm_next_scr(screenp))
	if (*screenp != screen)
	    sm_add_field_popup_item(sm_fld_open(field_record, "key"),
				    (*screenp)->screen_name);

    smdb_seek(field_record, index);
    smdb_set_file_unchanged(field_record);

    sm_add_callback(finish_field_edt, field_edt);
    if (sm_setup_edit(SM_BASE_WINDOW,
		      field_record, 
		      PREV_KEYS, 
		      NULL,
		      edit_field_skey,
		      screen) == SM_ERROR)
	sm_destroy_callback();
}

static void move_word_right ARG3(SM_SCREEN, screen, int *, row, int *, col)
{
    SM_BACKGROUND	bg = NULL;
    SM_FIELD		field, *fieldp;
    int			maxcol, maxrow, newcol, newrow;
    CHAR		*p;
    
    assert(screen);

    sm_enq_term(-1, &maxrow, &maxcol);
    
    newrow = *row;
    newcol = *col;
    
    /* Look at bg */
    while (newrow < maxrow - 1) {
        if ((bg = sm_pick_bg(screen, newrow)) == NULL) {
            newrow++;
	    newcol = 0;
        } else if (newcol >= StringLength(bg->text)) {
            if (newcol < maxcol - 1)
                newcol++;
            else {
                newcol = 0;
                newrow++;
            }
        } else
            break;
    }
    
    if (bg && (StringLength(bg->text) > newcol)) {
        p = StringToStr(bg->text) + newcol;
        p = sm_right_word(p, p);
        newcol = p - StringToStr(bg->text);
    }
    
    /* Now look at fields */
    while (((fieldp = sm_pick_fld(screen, *row, *col)) == NULL)
           && ((*row < maxrow - 1) || (*col < maxcol - 1))) {
        if (*col < maxcol - 1)
            (*col)++;
        else if (*row < maxrow - 1) {
            *col = 0;
            (*row)++;
        }
    }

    if (fieldp) {
	field = *fieldp;
        *row = field->row;
        *col = field->col + abs(field->len);
        if (*col >= maxcol)
            if (*row < maxrow - 1) {
                (*row)++;
                *col = 0;
            }
    }
    
    /* Use the least of bg or field-end */
    if ((newrow < *row) || 
        ((newrow == *row) && (newcol < *col))) {
        *row = newrow;
        *col = newcol;
    }
}

static void move_word_left ARG3(SM_SCREEN, screen, int *, row, int *, col)
{
    SM_BACKGROUND	bg = NULL;
    SM_FIELD		field, *fieldp;
    int			maxcol, maxrow, newcol, newrow;
    CHAR		*p;
    
    assert(screen);

    sm_enq_term(-1, &maxrow, &maxcol);
    
    newrow = *row;
    newcol = *col;
    
    /* Look at bg */
    while (1) {
        if ((bg = sm_pick_bg(screen, newrow)) == NULL) {
            if (newrow == 0)
                break;
            newrow--;
            newcol = maxcol - 1;
        } else if (newcol == 0) {
	    newcol = maxcol - 1;
	    newrow--;
        } else
            break;
    }
    
    if (bg && (newcol > 0)) {
        if (newcol > StringLength(bg->text))
            newcol = StringLength(bg->text) - 1;
        p = StringToStr(bg->text) + newcol;
        p = sm_left_word(StringToStr(bg->text), p);
        newcol = p - StringToStr(bg->text);
    }
    
    /* Now look at fields */
    
    /* Are we already at the start of a field? */
    if ((fieldp = sm_pick_fld(screen, *row, *col)) != NULL) {
	field = *fieldp;
        if ((field->row == *row) && (field->col == *col)) {
	    if (*col)
		(*col)--;
	    else if (*row) {
		(*col) = maxcol - 1;
		(*row)--;
	    }
	}
    }
    
    while (((fieldp = sm_pick_fld(screen, *row, *col)) == NULL)
           && ((*row > 0) || (*col > 0))) {
        if (*col > 0)
            (*col)--;
        else if (*row) {
            *col = maxcol - 1;
            (*row)--;
        }
    }

    if (fieldp) {
	field = *fieldp;
        *row = field->row;
        *col = field->col;
    }
    
    /* Use the greatest of bg or field-end */
    if ((newrow > *row) || 
        ((newrow == *row) && (newcol > *col))) {
        *row = newrow;
        *col = newcol;
    }
}

static int do_move ARG4(SM_SCREEN, screen, int, command, int *, row, int *, col)
{
    SM_BACKGROUND	*bgp, bg;
    SM_FIELD		*fieldp;
    int			maxrow, maxcol, time_delta, i;
    
    assert(screen);

    sm_enq_term(-1, &maxrow, &maxcol);
    
    switch (command) {
    case SM_LEFT_C:
	if (*col)
	    (*col)--;
	else
	    *col = maxcol - 1;
	break;
	
    case SM_RIGHT_C:
	if (*col < maxcol - 1)
	    (*col)++;
	else
	    *col = 0;
	break;
	
    case SM_UP_C:
	if (*row)
	    (*row)--;
	else
	    *row = maxrow - 1;
	break;
	
    case SM_DOWN_C:
	if (*row < maxrow - 1)
	    (*row)++;
	else
	    *row = 0;
	break;
	
    case SM_HOME_FIELD_C:
	*col = 0;
	break;
	
    case SM_END_FIELD_C:
	*col = 0;
	bgp = sm_first_bg(screen);
	fieldp = sm_first_fld(screen);
	if (((bgp == NULL) || (*bgp == NULL)) &&
	    ((fieldp == NULL) || (*fieldp == NULL)))
	    *row = maxcol - 1;

	if ((bg = sm_pick_bg(screen, *row))) {
	    i = StringLength(bg->text);
	    if (i > *col)
		*col = i;
	}

	for (fieldp = sm_first_fld(screen); 
	     fieldp && *fieldp; 
	     fieldp = sm_next_fld(fieldp)) {
	    if ((*fieldp)->row == *row) {
		i = (*fieldp)->col + abs((*fieldp)->len);
		if (i > *col)
		    *col = i;
	    }
	}
	break;
	
    case SM_NEXT_LINE_C:	/* CR/LF etc */
	return(0); /* Finishes MOVE_FLD */
	
    case SM_LEFT_WORD_C:
	move_word_left(screen, row, col);
	break;
	
    case SM_RIGHT_WORD_C:
	move_word_right(screen, row, col);
	break;
	
    case SM_HOME_SCREEN_C:
	*row = 0;
	*col = 0;
	break;
	
    case SM_END_SCREEN_C:
	*row = maxrow - 1;
	*col = maxcol - 1;
	break;
	
    case SM_NEXT_FIELD_C: /* tab */
	*col = ((*col / 8) + 1) * 8;
	if (*col > maxcol - 1)
	    *col = maxcol - 1;
	break;

    case SM_MOUSE_EVENT_C:
	// button =
	sm_enq_mouse(row, col, &time_delta);
	break;

    case SM_CANCEL_C:
	return(-1); /* cancels MOVE_FLD */

    default:
	return(command);
    }
    return(1); /* Continues MOVE_FLD */
}

static SM_SOFTKEY present_screen_keys = NULL;
static void load_screen_keys()
{	
    present_screen_keys = screen_skey;
    sm_paint_skey(screen_skey);
}

static void load_screen_field_keys()
{	
    present_screen_keys = screen_field_skey;
    sm_paint_skey(screen_field_skey);
}

static void sm_tell_screenname ARG1(CHAR *, screen_name)
{
    CHAR	*buf = alloca(200);

    if (screen_name) {
	sprintf(buf, "Screen-name: %s", screen_name);
	sm_message(buf);
    }
}

struct import_export_s {
    f_ptr	destructor;
    int		command;
    SM_SCREEN	screen;
};

static int import_export_destructor ARG1(struct import_export_s *, p)
{
    free(p);
    return(0);
}

static struct import_export_s *create_import_export()
{
    struct import_export_s *p;

    p = (struct import_export_s *) xmalloc((INDEX) sizeof(struct import_export_s));
    p->destructor = import_export_destructor;
    return(p);
}

static int export_file ARG2(FILE *, 			f, 
			    struct import_export_s *, 	import_export)
{
    int			i, row;
    SM_FIELD		*fieldp;
    SM_BACKGROUND	*bgp;
    CHAR		*buf = alloca(SM_MAXREC);
    CHAR		*d, *p, screen_char = SM_SCREEN_CHAR;

    /* Writing the screen out */
    
    /* First - sort the background and fields */
    sm_check_screens(sm_user_screens());
    
    row = 0;
    bgp = sm_first_bg(import_export->screen);
    fieldp = sm_first_fld(import_export->screen);
    while ((bgp && *bgp) || (fieldp && *fieldp)) {
	/* Start with a blank line */
	for (i = 0; i < SM_MAXREC; i++)
	    buf[i] = ' ';
	buf[SM_MAXREC - 1] = 0;
	
	/* Put the backgrounds in */
	while (bgp && *bgp && ((*bgp)->row == row)) {
	    d = buf;
	    p = StringToStr((*bgp)->text);
	    while (p && *p)
		*d++ = *p++;
	    bgp = sm_next_bg(bgp);
	}
	/* Put the fields in - but don't overwrite background */
	while (fieldp && *fieldp && ((*fieldp)->row == row)) {
	    CHAR	*p, *end;
	    
	    if (!((*fieldp)->hidden)) {
		p = buf + (*fieldp)->col;
		end = p + abs((*fieldp)->len);     
		while (p < end) {
		    if (*p == ' ')
			*p = screen_char;
		    p++;
		}
	    }
	    fieldp = sm_next_fld(fieldp);
	}
	/* right trim the line */
	for (p = buf + SM_MAXREC - 1; 
	     (p >= buf) && ((*p == 0) || isspace(*p)); 
	     p--)
	    *p = 0;
	/* Out it goes */
	if (*buf)
	    if (fputs(buf, f) == EOF)
		return(SM_ERROR);
	if (fputc('\n', f) == EOF)
	    return(SM_ERROR);
	row++;
    }
    return(0);
}

CALLBACK(import_export_done)
{    
    struct import_export_s 
	*import_export = (struct import_export_s *) callback_data;
    CHAR		*filename = alloca(MAXPATHLEN + 1), *p;
    CHAR                *buf = alloca(SM_MAXREC);
    CHAR		screen_char = SM_SCREEN_CHAR;
    FILE		*f = NULL;
    time_t		old_time = 0;
    struct stat		statbuf;
    int			row;
    SM_BACKGROUND	bg;

    /* Called directly or callback after sm_prompt */
    if (command == SM_CANCEL_C)
	return(SM_POPCALLBACK);
    else if ((command == SM_EXIT_C) || (command == SM_NO_SAVE_EXIT_C))
	return(command);

    if (import_export->command == EXP_EDIT_SCREEN)
	sm_mktemp(filename, "EXXXXXX");
    else
	filename = sm_get_prompt_value();

    if (import_export->command == EXPORT_SCREEN) {
	/* if file is readable, make sure we want to overwrite it */
	if ((f = fopen(filename, "r")) != NULL) {
	    fclose(f);
	    if (sm_confirm("File already exists. Overwrite?") != SM_YES_C)
		return(SM_POPCALLBACK);
	}
    }    

    f = NULL;
    if (((import_export->command == EXP_EDIT_SCREEN) || 
	 (import_export->command == EXPORT_SCREEN)) && *filename) {
	if (((f = fopen(filename, "w")) == NULL) ||
	    (export_file(f, import_export) != 0)) {
	    if (f)
		fclose(f);
	    sm_file_error("Can't write to file: ", filename, errno);
	    return(SM_POPCALLBACK);
	}
    }
    if (f)
        fclose(f);
    f = NULL;
    
    if (import_export->command == EXP_EDIT_SCREEN) {
	int	redirect_stderr = 0;

	/* Get the mod. time before calling the editor */
	stat(filename, &statbuf);
	old_time = statbuf.st_mtime;

	/* shell escape to the editor */
	if (((p = getenv("EDITOR")) != NULL) && *p)
	    strcpy(buf, p);
	else {
#if NEEDS_PATH_FOR_PROGS
	    strcpy(buf, sm_program_directory);
#endif
#if USE_SCRMGR_PROGS
	    strcpy(buf, "smedit");
	    redirect_stderr = 1;
#else
	    strcpy(buf, "vi");
#endif
	}
	strcat(buf, " ");
	strcat(buf, filename);
	// result =
	smio_system(buf, redirect_stderr);
	
	/* Get the new mod. time */
	stat(filename, &statbuf);
    }
    
    if ((import_export->command == IMPORT_SCREEN) || 
	((import_export->command == EXP_EDIT_SCREEN) && 
	 /* (result == 0) && ... some vi's (e.g. elvis) return a value on non-errors*/
	 (statbuf.st_mtime != old_time))) {
	/* read file in */
	if ((f = fopen(filename, "r")) == NULL) {
	    sm_file_error("Can't open file: ", filename, errno);
	    return(SM_POPCALLBACK);
	}
	
	sm_paint_scr(SM_BASE_WINDOW, NULL, 0);
	/* Clear out the old screen background */
	sm_destroy_all_bgs(import_export->screen);

	dirty_data = 1;
	
	/* Read in the new */
	row = 0;
	while (fgets(buf, SM_MAXREC, f)) {
	    while ((p = strchr(buf, '\n')) != NULL)
		*p = 0;
	    while ((p = strchr(buf, '\r')) != NULL)
		*p = 0;

	    /* Remove the traces of any previous fields */
	    while ((p = strchr(buf, screen_char)) != NULL)
		*p = ' ';
            /* remove trailing spaces */
            if (*buf)
                for (p = buf + strlen(buf) - 1; p >= buf; p--)
                    if (*p == ' ')
                        *p = 0;
                    else
                        break;

	    if (*buf) {
		bg = sm_create_bg(import_export->screen);
		bg->row = row;
		bg->text = StrToString(NULL, buf);
	    }
	    row++;
	}
	
	fclose(f);
    }

    /* Remove the temporary file */
    if (import_export->command == EXP_EDIT_SCREEN)
	unlink(filename);

    return(SM_POPCALLBACK);
}

struct order_edt_s {
    f_ptr	destructor;
    SM_SCREEN	dummy_scr;
    INDEX	index;
    SM_SCREEN	screen;
};

static int order_edt_destructor ARG1(struct order_edt_s *, object)
{
    smdb_delete_data(object->dummy_scr);
    free(object);
    return(0);
}

static CHAR order_edt_msg[] =
"Arrange fields in file order: + - $ [0-9] R:Restores ^C:cancels RETURN:accepts";

static int order_edt_helper ARG6(int, 		command, 
				 ARRAY *, 	data_in, 
				 INDEX *, 	index_in, 
				 int, 		row, 
				 int, 		col,
				 void *,	dummy)
{
    ARRAY 	data = *data_in, /* To simplify! */
		*p;
    INDEX	index     = *index_in,
		new_index = *index_in,
		i;

    switch (command) {
    case ORDER_PROMOTE:
    case '+':
	if (arrayItem(data, index) && arrayItem(data, index + 1)) {
	    p = arrayItem(data, index);
	    setItem(data, index, arrayItem(data, index + 1));
	    setItem(data, index + 1, p);
	    index++;
	}
	command = 0;
	break;

    case ORDER_DEMOTE:
    case '-':
	if (index) {
	    p = arrayItem(data, index);
	    setItem(data, index, arrayItem(data, index - 1));
	    setItem(data, index - 1, p);
	    index--;
	}
	command = 0;
	break;

    case ORDER_FIRST:
	command = '0';
    case ORDER_LAST:
	if (command == ORDER_LAST)
	    command = '$';
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
    case '$':
	if ((p = arrayItem(data, index))) {

	    if (command == '$')
		new_index = MAX_INDEX;
	    else
		new_index = command - '0';

	    if (new_index > index) {
		INDEX	i;

		for (i = index; arrayItem(data, i + 1) && (i < new_index); i++)
		    setItem(data, i, arrayItem(data, i + 1));
		new_index = i;

	    } else if (new_index < index) {
		for (i = index; i && (i > new_index); i--)
		    setItem(data, i, arrayItem(data, i - 1));
		new_index = i;

	    } else
		new_index = index;

	    setItem(data, new_index, p);
	}
	index = new_index;
	command = 0;
	break;

    case SM_QUIT_C:
	/* Confirm quit */
	break;

    case ORDER_RESTORE:
    case 'r':
    case 'R':
	/* let pass to parent */
	command = ORDER_RESTORE;
	index = 0;
	break;

    case ORDER_ACCEPT:
    case SM_NEXT_LINE_C:
	command = ORDER_ACCEPT;
	break;

    default:
	break;
    }

    *index_in = index;
    *data_in = data;

    sm_message(order_edt_msg);
    return(command);
}

CALLBACK(do_order_edt)
{
    struct order_edt_s *order_edt = (struct order_edt_s *) callback_data;

    if (command == ORDER_ACCEPT) {
	f_ptr	f;

	f = (f_ptr) free;
	sm_message("New field ordering saved in screen definition.");
	if (order_edt->screen->data_field) {
	    removeAllItemsFromArray(order_edt->screen->data_field, f);
	    freeArray(order_edt->screen->data_field);
	}

	order_edt->screen->data_field = order_edt->dummy_scr->data;
	/* Make sure that object_edt->destructor does not trash the data - it
	   now belongs on the screen, not on object_edt ... */
	order_edt->dummy_scr->data = NULL;
	dirty_data = 1;
	return(SM_POPCALLBACK);
    } else if (command == ORDER_RESTORE) {
	/* Restore original and go back to it */
	
	SM_FIELD	*fp;
	
	/* Rebuild from screen ordering */
	smdb_delete_data(order_edt->dummy_scr);
	for (fp = sm_first_fld(order_edt->screen);
	     fp && *fp;
	     fp = sm_next_fld(fp))
	    smdb_write(order_edt->dummy_scr, (*fp)->field_name);
	smdb_seek(order_edt->dummy_scr, (INDEX) 0);
	sm_setup_selection(order_edt->dummy_scr, 
			   &order_edt->index,
			   0, 
			   5, 
			   order_edt_helper,
			   NULL,
			   order_fields_skey);
	return(0);
    } else {
	if (command == SM_QUIT_C)
	    command = SM_POPCALLBACK;
	sm_message("Field re-ordering cancelled.");
	return(command);
    }
}

static void setup_order_edt ARG1(SM_SCREEN, screen)
{
    CHAR	**p;
    SM_FIELD	*fp;
    struct order_edt_s	*order_edt;

    assert(screen);

    order_edt = (struct order_edt_s *)xmalloc((INDEX) sizeof(struct order_edt_s));
    order_edt->destructor = order_edt_destructor;
    order_edt->screen = screen;
    order_edt->dummy_scr = sm_prompt_scr; /* Kludge !! */
    if (sm_prompt_scr->data) {
	f_ptr	f;

	f = (f_ptr) free;
	removeAllItemsFromArray(sm_prompt_scr->data, f);
	freeArray(sm_prompt_scr->data);
    }

    /* Build list for this screen */
    if (order_edt->screen->data_field == NULL) {

	/* Build from field order */
	for (fp = sm_first_fld(order_edt->screen);
	     fp && *fp;
	     fp = sm_next_fld(fp)) {
	    smdb_write(order_edt->dummy_scr, (*fp)->field_name);
	}
    } else {
	sm_check_data_field(order_edt->screen);

	/* Build from existing ordering */
	for (p = (CHAR **) firstArrayItemp(order_edt->screen->data_field);
	     p && *p;
	     p = (CHAR **) nextArrayItemp(order_edt->screen->data_field, 
					  (void **)p)) {
	    smdb_write(order_edt->dummy_scr, *p);
	}
    }

    order_edt->index = 0;
    sm_message(order_edt_msg);
    sm_add_callback(do_order_edt, order_edt);
    sm_setup_selection(order_edt->dummy_scr, 
		       &order_edt->index,
		       0, 
		       5, 
		       order_edt_helper,
		       NULL,
		       order_fields_skey);
}	

struct screen_edt_s {
    f_ptr	destructor;
    SM_SCREEN	*current_scr;
    int		row, col, attr, maxcol, maxrow, old_row, old_col;
    SM_FIELD	field;
};

static int screen_edt_destructor ARG1(struct screen_edt_s *, object)
{
    free(object);
    return(0);
}

CALLBACK(move_line)
{
    struct screen_edt_s *screen_edt = (struct screen_edt_s *) callback_data;
    SM_FIELD		*fieldp;
    SM_BACKGROUND	*bgp;
	    
    command = do_move(*CURRENT_SCR, 
		      command, 
		      &screen_edt->row, 
		      &screen_edt->col);
    smio_goto(SM_BASE_WINDOW, screen_edt->row, screen_edt->col);
    if (command == 0) {
	dirty_data = 1;
	for (fieldp = sm_first_fld(*CURRENT_SCR);
	     fieldp && *fieldp;
	     fieldp = sm_next_fld(fieldp)) {
	    if ((*fieldp)->row == screen_edt->old_row)
		(*fieldp)->row = screen_edt->row;
	}
	for (bgp = sm_first_bg(*CURRENT_SCR);
	     bgp && *bgp;
	     bgp = sm_next_bg(bgp)) {
	    if ((*bgp)->row == screen_edt->old_row)
		(*bgp)->row = screen_edt->row;
	}
	return(SM_REFRESH_C);
    } else if (command == 1)
	return(0);
    else if (command == -1)
	return(SM_POPCALLBACK);
    else
	return(command);
}

CALLBACK(move_fld)
{
    struct screen_edt_s *screen_edt = (struct screen_edt_s *) callback_data;

    command = do_move(*CURRENT_SCR, 
		      command, 
		      &screen_edt->row, 
		      &screen_edt->col);
    smio_goto(SM_BASE_WINDOW, screen_edt->row, screen_edt->col);
    if (command == 0) {
	screen_edt->field->row = screen_edt->row;
	screen_edt->field->col = screen_edt->col;
	dirty_data = 1;
	return(SM_REFRESH_C);
    }  else if (command == 1)
	return(0);
    else if (command == -1) {
	screen_edt->row = screen_edt->field->row;
	screen_edt->col = screen_edt->field->col;
	smio_goto(SM_BASE_WINDOW, screen_edt->row, screen_edt->col);
	return(SM_POPCALLBACK);
    } else
	return(command);
}

CALLBACK(repaint_all)
{
    struct screen_edt_s *screen_edt = (struct screen_edt_s *) callback_data;

    sm_paint_scr(SM_BASE_WINDOW, NULL, 1);
    sm_calc_scrolling_line(*CURRENT_SCR);
    sm_paint_scr(SM_BASE_WINDOW, 
		 *CURRENT_SCR, 
		 /* enhance = */ 1);
    sm_paint_skey(present_screen_keys);
    return(command);
}

CALLBACK(do_screen_edt)
{
    struct screen_edt_s	*screen_edt = (struct screen_edt_s *) callback_data;
    int 		repaint;
    SM_FIELD		*fieldp, field;
    SM_SCREEN		*temp;
    
    alloca(0);
    
    /* Make sure scrolling field is still OK */
    sm_calc_scrolling_line(*CURRENT_SCR); 
    if (CURRENT_SCR && 
	*CURRENT_SCR && 
	(*CURRENT_SCR)->scrolling)
	(*CURRENT_SCR)->current_line = 0;
    
    repaint = NO_REPAINT;
    
    /* Default message */
    if (CURRENT_SCR && *CURRENT_SCR)
	sm_tell_screenname((*CURRENT_SCR)->screen_name);
    
    sm_paint_skey(present_screen_keys);

    switch (command) {
    case SM_NEXT_SCREEN_C:
	temp = sm_next_scr(CURRENT_SCR);
	if (temp && *temp) {
	    CURRENT_SCR = temp;
	    repaint = REPAINT_ALL;
	    sm_tell_screenname((*CURRENT_SCR)->screen_name);
	} else
	    sm_message("No next screen!");
	break;
	
    case SM_PREV_SCREEN_C:
	temp = sm_prev_scr(CURRENT_SCR);
	if (temp && *temp) {
	    CURRENT_SCR = temp;
	    repaint = REPAINT_ALL;
	    sm_tell_screenname((*CURRENT_SCR)->screen_name);
	} else
	    sm_message("No previous screen!");
	break;
	
    case SM_FIRST_SCREEN_C:
	temp = sm_first_scr(sm_user_screens());
	if (temp && *temp) {
	    CURRENT_SCR = temp;
	    repaint = REPAINT_ALL;
	    sm_tell_screenname((*CURRENT_SCR)->screen_name);
	} else
	    sm_message("No first screen!");
	break;
	
    case SM_LAST_SCREEN_C:
	if (CURRENT_SCR) {
	    do {
		temp = sm_next_scr(CURRENT_SCR);
		if (temp && *temp)
		    CURRENT_SCR = temp;
	    } while (temp && *temp);
	    repaint = REPAINT_ALL;
	    sm_tell_screenname((*CURRENT_SCR)->screen_name);
	} else
	    sm_message("No last screen!");
	break;
	
    case SM_LEFT_C:
    case SM_RIGHT_C:
    case SM_HOME_FIELD_C:
    case SM_UP_C:
    case SM_DOWN_C:
    case SM_END_FIELD_C:
    case SM_LEFT_WORD_C:
    case SM_RIGHT_WORD_C:
    case SM_HOME_SCREEN_C:
    case SM_END_SCREEN_C:
    case SM_NEXT_FIELD_C:	/* tab */
    case SM_MOUSE_EVENT_C:
	if (CURRENT_SCR)
	    do_move(*CURRENT_SCR, 
		    command, 
		    &screen_edt->row, 
		    &screen_edt->col);
	break;
	
    case SM_NEXT_LINE_C:	/* CR/LF etc */
	if (CURRENT_SCR && 
	    *CURRENT_SCR && 
	    (screen_edt->row < screen_edt->maxrow - 1)) {
	    if (smio_insertp()) {
		sm_split_line(*CURRENT_SCR, 
			      screen_edt->row, screen_edt->col);
		repaint = REPAINT_ALL;
		dirty_data = 1;
	    }
	    screen_edt->row++;
	}
	screen_edt->col = 0;
	break;
	
    case SM_DEL_BACK_C:
	if (screen_edt->col) {
	    screen_edt->col--;
	    if (CURRENT_SCR && 
		*CURRENT_SCR) {
		sm_del_char(*CURRENT_SCR, 
			    screen_edt->row, 
			    screen_edt->col);
		dirty_data = 1;
		repaint = REPAINT_LINE;
	    }
	}
	break;
	
    case SM_DEL_CHAR_C:
	if (CURRENT_SCR && 
	    *CURRENT_SCR) {
	    sm_del_char(*CURRENT_SCR, 
			screen_edt->row, 
			screen_edt->col);
	    dirty_data = 1;
	    repaint = REPAINT_LINE;
	}
	break;
	
    case SM_CLEAR_FIELD_C:
	/* Clear the rest of the line. If there is nothing
	   there, join the next line to this */
	if (CURRENT_SCR && 
	    *CURRENT_SCR) {
	    int result = sm_clear_line(*CURRENT_SCR, 
				       screen_edt->row, 
				       screen_edt->col);
	    repaint = REPAINT_LINE;
	    
	    if (result == 0) {
		/* Nothing deleted yet */
		sm_join_lines(*CURRENT_SCR, 
			      screen_edt->row, 
			      screen_edt->col);
		repaint = REPAINT_ALL;
	    }
	    sm_check_data_field(*CURRENT_SCR);
	    dirty_data = 1;
	}
	break;
	
    case SM_CLEAR_SCREEN_C:
	if (CURRENT_SCR && 
	    *CURRENT_SCR) {
	    int		i;

	    sm_clear_line(*CURRENT_SCR, 
			  screen_edt->row, 
			  screen_edt->col);
	    for (i = screen_edt->row; i < screen_edt->maxrow; i++)
		sm_clear_line(*CURRENT_SCR, i, 0);
	    repaint = REPAINT_ALL;
	    sm_check_data_field(*CURRENT_SCR);
	    dirty_data = 1;
	}
	break;
	
    case SM_TOGGLE_CASE_C:
	if (CURRENT_SCR && 
	    *CURRENT_SCR) {
	    /* Unfinished ... */
	    repaint = REPAINT_LINE;
	    dirty_data = 1;
	}
	break;
	
    case SM_DEL_WORD_C:
	if (CURRENT_SCR && 
	    *CURRENT_SCR) {
	    /* Unfinished */
	    repaint = REPAINT_LINE;
	    dirty_data = 1;
	}
	break;
	
    case SM_TRANSPOSE_WORDS_C:
	if (CURRENT_SCR && 
	    *CURRENT_SCR) {
	    /* Unfinished */
	    repaint = REPAINT_LINE;
	    dirty_data = 1;
	}
	break;
	
    case CREATE_FLD:
	if (!(CURRENT_SCR) || !*CURRENT_SCR)
	    CURRENT_SCR = sm_create_scr(sm_user_screens());
	
	fieldp = sm_create_fld(*CURRENT_SCR);
	field = *fieldp;
	
	field->row = screen_edt->row;
	field->col = screen_edt->col;
	field->len = 0;
	field->attribute = 1;
	sm_add_callback(repaint_all, NULL);
	setup_field_edt(*CURRENT_SCR, field);
	return(0);
	
    case DELETE_FLD:
	if (CURRENT_SCR && *CURRENT_SCR) {
	    if ((fieldp = sm_pick_fld(*CURRENT_SCR, 
				      screen_edt->row, 
				      screen_edt->col)) != NULL) {
		sm_destroy_fld(fieldp);
		dirty_data = 1;
		repaint = REPAINT_LINE;
		sm_check_data_field(*CURRENT_SCR);
	    } else 
		sm_message("I see no field!");
	}			
	break;
	
    case EDIT_FLD:
	if (CURRENT_SCR && *CURRENT_SCR) {
	    if ((fieldp = sm_pick_fld(*CURRENT_SCR, 
				      screen_edt->row, 
				      screen_edt->col)) != NULL) {
		sm_add_callback(repaint_all, NULL);
		setup_field_edt(*CURRENT_SCR, *fieldp);
		return(0);
	    } else 
		sm_message("I see no field!");
	}			
	break;
	
    case MOVE_LINE:
	if (CURRENT_SCR && *CURRENT_SCR) {
	    screen_edt->old_row = screen_edt->row, 
	    screen_edt->old_col = screen_edt->col;
	    sm_add_callback(move_line, NULL);
	    sm_message("Move to new line and press ENTER or RETURN (^C cancels) ");
	}
	break;
	
    case MOVE_FLD:
	if (CURRENT_SCR && *CURRENT_SCR) {
	    if ((fieldp = sm_pick_fld(*CURRENT_SCR, 
				      screen_edt->row, 
				      screen_edt->col)) == NULL) 
		sm_message("I see no field!");
	    else {
		screen_edt->field = *fieldp;
		screen_edt->col =  screen_edt->field->col;
		sm_add_callback(move_fld, NULL);
		sm_message("Move to new position and press ENTER or RETURN (^C cancels) ");
	    }
	}
	break;
	
    case ORDER_FLDS:
	if (CURRENT_SCR && *CURRENT_SCR) {
	    sm_add_callback(repaint_all, NULL);
	    setup_order_edt(*CURRENT_SCR);
	    return(0);
	}
	break;
	
    case SCREEN_NAME:
	if ((CURRENT_SCR == NULL) || 
	    (*CURRENT_SCR == NULL))
	    CURRENT_SCR = sm_create_scr(sm_user_screens());
	sm_add_callback(repaint_all, NULL);
	setup_scr_name_edt(*CURRENT_SCR);
	return(0);
	
    case DELETE_SCREEN:
	if (CURRENT_SCR && 
	    *CURRENT_SCR && 
	    sm_confirm("Really delete this screen?") == SM_YES_C) {
	    sm_destroy_scr(CURRENT_SCR);
	    /* sm_destroy_scr moves the next screen down */

	    if ((CURRENT_SCR == NULL) ||
		(*CURRENT_SCR == NULL))
		CURRENT_SCR = sm_prev_scr(CURRENT_SCR);

	    if ((CURRENT_SCR == NULL) ||
		(*CURRENT_SCR == NULL))
		CURRENT_SCR = sm_create_scr(sm_user_screens());
	    sm_tell_screenname((*CURRENT_SCR)->screen_name);
	    dirty_data = 1;
	    repaint = REPAINT_ALL;
	}
	break;
	
    case CREATE_SCREEN:
	CURRENT_SCR = sm_create_scr(sm_user_screens());
	sm_add_callback(repaint_all, NULL);
	setup_scr_name_edt(*CURRENT_SCR);
	return(0);

    case SM_QUIT_C:
    case PREV_KEYS:
	sm_message("");
	return(command);
	
    case FIELD_KEYS:
	load_screen_field_keys();
	break;
	
    case SCREEN_KEYS:
	load_screen_keys();
	break;
	
    case IMPORT_SCREEN:
	if ((CURRENT_SCR == NULL) ||
	    (*CURRENT_SCR == NULL))
	    CURRENT_SCR = sm_create_scr(sm_user_screens());
	if ((sm_first_bg(*CURRENT_SCR) == NULL) ||
	    (sm_confirm("Overwrite current screen?") == SM_YES_C)) {
	    struct import_export_s *	import_export;
	    CHAR 			pwd[MAXPATHLEN + 1];

	    import_export = create_import_export();
	    import_export->command = command;
	    import_export->screen = *CURRENT_SCR;
	    sm_add_callback(repaint_all, NULL);
	    sm_add_callback(import_export_done, import_export);
	    GETCWD(pwd, MAXPATHLEN);
	    sm_setup_prompt("Enter filename to import (current directory is:)", 
			    pwd, 
			    "",
			    NULL);
	    return(0);
	}
	break;
	
    case EXPORT_SCREEN:
    case EXP_EDIT_SCREEN:
	if (CURRENT_SCR && *CURRENT_SCR) {
	    struct import_export_s *	import_export;

	    import_export = create_import_export();
	    import_export->command = command;
	    import_export->screen = *CURRENT_SCR;
	    if (command == EXPORT_SCREEN) {
		CHAR	pwd[MAXPATHLEN + 1];

		sm_add_callback(import_export_done, import_export);
		GETCWD(pwd, MAXPATHLEN);
		sm_setup_prompt("Enter filename for screen (current directory is:)", 
				pwd, "", NULL);
		return(0);
	    } else {
		import_export_done(SM_NOT_A_CALLBACK, import_export);
		(*(import_export->destructor))(import_export);
		repaint = REPAINT_ALL;
	    }
	}
	break;

    case SM_REFRESH_C:
	repaint = REPAINT_ALL;
	break;

    case SM_EXIT_C:
    case SM_NO_SAVE_EXIT_C:
    case SM_STORE_FILE_C:
    case SM_STORE_FILE_AS_C:
    case SM_LOAD_FILE_C:
    case SM_CLEAR_FILE_C:
	return(command);

    default:

	/* Specifically exclude the ~ character as it is used as a
	   separator in the screen files. If the separator character
	   for scrmgr screen files is changed then this must be
	   changed too */
	if ((command >= ' ') && 
	    (command <= '~') && 
	    (command != sm_default_separator)) {
	    if ((CURRENT_SCR == NULL) ||
		(*CURRENT_SCR == NULL))
		CURRENT_SCR = sm_create_scr(sm_user_screens());
	    if (sm_add_char(*CURRENT_SCR, 
			    screen_edt->row, 
			    screen_edt->col, 
			    command, 
			    screen_edt->attr) >= 0) {
		repaint = REPAINT_LINE;
		screen_edt->col++;
	    }
	    dirty_data = 1;
	}
	break;
    }
    if (CURRENT_SCR && *CURRENT_SCR) {
	if (repaint == REPAINT_ALL) {
	    sm_paint_scr(SM_BASE_WINDOW, NULL, /* enhance = */ 1);
	    sm_calc_scrolling_line(*CURRENT_SCR);
	    sm_paint_scr(SM_BASE_WINDOW, 
			 *CURRENT_SCR, 
			 /* enhance = */ 1);
	    sm_paint_skey(present_screen_keys);
	} else if (repaint == REPAINT_LINE) {
	    sm_calc_scrolling_line(*CURRENT_SCR);
	    sm_paint_part_scr(SM_BASE_WINDOW, 
			      *CURRENT_SCR, 
			      screen_edt->row);
	    sm_paint_skey(present_screen_keys);
	}
    } else
	sm_paint_scr(SM_BASE_WINDOW, NULL, /* enhance = */ 1);

    smio_goto(SM_BASE_WINDOW, screen_edt->row, screen_edt->col);
    
    return(0);
}

static void setup_screen_edt()
{
    struct screen_edt_s	*screen_edt;

    screen_edt = (struct screen_edt_s *)xmalloc((INDEX) sizeof(struct screen_edt_s));
    screen_edt->destructor = screen_edt_destructor;
    sm_enq_term(-1, &screen_edt->maxrow, &screen_edt->maxcol);
    screen_edt->row = screen_edt->col = screen_edt->attr = 0;

    if (((CURRENT_SCR = sm_first_scr(sm_user_screens())) == NULL) ||
	(*CURRENT_SCR == NULL))
	CURRENT_SCR = sm_create_scr(sm_user_screens());
    sm_tell_screenname((*CURRENT_SCR)->screen_name);
    sm_add_callback(do_screen_edt, screen_edt);

    sm_paint_scr(SM_BASE_WINDOW, NULL, /* enhance = */ 1);
    sm_calc_scrolling_line(*CURRENT_SCR);
    sm_paint_scr(SM_BASE_WINDOW, 
		 *CURRENT_SCR, 
		 /* enhance = */ 1);
    if (present_screen_keys == NULL)
	load_screen_field_keys();
    else
	sm_paint_skey(present_screen_keys);
}

CHAR	temp_sep;
CALLBACK(finish_global_edt)
{
    CHAR	*buf;
    int		changed;
    SM_SCREEN	*screenp;

    sm_scrmgr_screens()->separator = temp_sep;
    
    switch (command) {
    case PREV_KEYS:
    case SM_EXIT_C:
    case SM_NO_SAVE_EXIT_C:
    case SM_STORE_FILE_AS_C:
    case SM_STORE_FILE_C:
	buf = alloca(SM_MAXREC);
    	
	if (sm_scr_changed(file_record)) {
	    dirty_data = 1;
	    sm_screen_to_data(buf, file_record);
	    /* Read in the new */
	    sm_read_gbl(sm_user_screens(), buf);
	}
	break;

    case SM_QUIT_C:
	break;

    default:
	if (sm_scr_changed(file_record))
	    changed = 1;
	else
	    changed = 0;
	screenp = sm_first_scr(sm_user_screens());
	sm_setup_scr(SM_BASE_WINDOW, 
		     file_record, 
		     NULL, 
		     screenp? *screenp: NULL,
		     0,
		     0,
		     0);
	if (changed)
	    sm_set_scr_changed(file_record);
	command = 0;
	break;
    } 

    return(command);
}

static void setup_global_edt()
{
    CHAR	*buf = alloca(SM_MAXREC);
    SM_SCREEN	*screenp;

    sm_format_file(sm_user_screens(), buf);
    sm_data_to_screen(file_record, buf, 0);
    temp_sep = sm_scrmgr_screens()->separator;
    sm_scrmgr_screens()->separator = 0; 
    /* ... to allow 'separator' to be entered! */
    sm_add_callback(finish_global_edt, NULL);
    screenp = sm_first_scr(sm_user_screens());
    sm_setup_scr(SM_BASE_WINDOW, 
		 file_record, 
		 NULL, 
		 screenp? *screenp: NULL,
		 0,
		 0,
		 0);
}

static int softkey_lambda ARG2(int, command, SM_SCREEN, screen)
{
    /* No other keys needed */
    return(command);
}

CALLBACK(finish_softkey_edt)
{
    alloca(0);

    if (command != SM_QUIT_C) {
	if (smdb_file_changed(key_record)) {
	    dirty_data = 1;

	    /* Out with the old ... */
	    sm_destroy_all_skeys(sm_user_screens());
    
	    /* ... in with the new ... store keys in structures */
	    sm_dbopen(sm_user_screens(), (CHAR **)firstArrayItemp(key_record->data));
	}
    } else
	command = PREV_KEYS;

    /* Tear down data ... */
    smdb_delete_data(key_record);
    return(command);
}

static void setup_softkey_edt()
{
    CHAR	*buf;
    SM_SOFTKEY	*skeyp;

    buf = alloca(SM_MAXREC);
    	
    sm_paint_scr(SM_BASE_WINDOW, key_record, /* enhance = */ 1);
    
    /* Create a SM_DATA structure based on the softkeys values */
    skeyp = sm_first_skey(sm_user_screens());
    while (skeyp && *skeyp) {	
        sm_format_skey(*skeyp, buf);
        smdb_write(key_record, buf);
        skeyp = sm_next_skey(skeyp);
    }

    smdb_set_file_unchanged(key_record);
    smdb_seek(key_record, (INDEX) 0);
    sm_add_callback(finish_softkey_edt, NULL);
    if (sm_setup_edit(SM_BASE_WINDOW, 
		      key_record, 
		      PREV_KEYS, 
		      softkey_lambda,
		      softkey_skey,
		      NULL) == SM_ERROR)
	sm_destroy_callback();
}

static int menu_lambda ARG2(int, command, SM_SCREEN, screen)
{
    /* No other keys needed */
    return(command);
}

CALLBACK(finish_menu_edt)
{
    // CHAR		*buf;

    // buf = alloca(SM_MAXREC);
    	
    alloca(0);

    switch (command) {
    case SM_QUIT_C:
	command = PREV_KEYS;
	break;

    case CREATE_MENU:
	smdb_seek(menu_record, MAX_INDEX);
	smdb_insert_rec(menu_record, "");
	command = 0;
	break;

    default:
	if (smdb_file_changed(menu_record)) {
	    dirty_data = 1;

	    /* Out with the old ... */
	    sm_destroy_all_menus(sm_user_screens());
    
	    /* ... in with the new ... store keys in structures */
	    sm_dbopen(sm_user_screens(), 
		      (CHAR **)firstArrayItemp(menu_record->data));
	}
    }

    /* Tear down data ... */
    if (command)
	smdb_delete_data(menu_record);
    else if (sm_setup_edit(SM_BASE_WINDOW, 
			   menu_record, 
			   PREV_KEYS,       
			   menu_lambda,
			   menu_skey,
			   NULL) == SM_ERROR)
	sm_destroy_callback();

    return(command);
}

static void  setup_menu_edt()
{
    CHAR		*buf;
    SM_MENU		*menup;
    INDEX		item;

    buf = alloca(SM_MAXREC);
    sm_calc_scrolling_line(menu_record);
    sm_paint_scr(SM_BASE_WINDOW, menu_record, /* enhance = */ 1);
    
    /* Create a SM_DATA structure based on the menus values */

    for (menup = sm_first_menu(sm_user_screens()); 
	 menup && *menup; 
	 menup = sm_next_menu(menup)) {
	for (item = 0; item < arrayLength((*menup)->items); item++) {
	    sm_format_menu(*menup, item, buf);
	    smdb_write(menu_record, buf);
	}
    }

    smdb_set_file_unchanged(menu_record);
    smdb_seek(menu_record, (INDEX) 0);
    sm_add_callback(finish_menu_edt, NULL);
    if (sm_setup_edit(SM_BASE_WINDOW, 
		      menu_record, 
		      PREV_KEYS,       
		      menu_lambda,
		      menu_skey,
		      NULL) == SM_ERROR)
	sm_destroy_callback();
}

struct load_file_s {
    f_ptr	destructor;
    int		clear_all;
};

static int load_file_destructor ARG1(struct load_file_s *, object)
{
    free(object);
    return(0);
}

CALLBACK(load_file)
{
    struct load_file_s 	*load_file_data = (struct load_file_s *) callback_data;
    CHAR		*filename = alloca(MAXPATHLEN + 1),
    			*direct   = alloca(MAXPATHLEN + 1);

    /* Always entered after getfilename() */
    if (command == SM_YES_C) {
	sm_getfilename(filename, direct);

	if (load_file_data->clear_all) {
	    sm_clear_out(0);
	    sm_message("File cleared");
	    /* See load_file -- we want to keep the filename if
	       we are merging in another file */
	    last_filename[0] = 0;
	    dirty_data = 0;
	}

	if (*filename) {
	    if (chdir(direct)) {
		sm_message("No such directory");
		return(SM_ERROR);
	    }
	    if (sm_fopen(sm_user_screens(), filename) != SM_ERROR) {
		/* Only use the new filename if there was no old one. Then,
		   if he uses this to merge in a file, we preserve the old
		   filename rather than switch to a new one */
		if (last_filename[0] == 0) {
		    strcpy(last_filename, filename);
		    strcpy(last_direct, direct);
		}
		sprintf(direct, "\"%s\" loaded.", filename);
		sm_message(direct);
	    } else
		sm_error("Error reading screen file");
	}
    }
    return(SM_YES_C); /* To provoke reentry into main_loop */
}

static int setup_load_file()
{
    struct load_file_s *load_file_data;
    int			clear_it = SM_YES_C;

    load_file_data = (struct load_file_s *)xmalloc((INDEX) sizeof(struct load_file_s));
    load_file_data->destructor = load_file_destructor;
    sm_add_callback(load_file, load_file_data);
    if (sm_anything_there(sm_user_screens()) &&
	((clear_it = 
	     sm_confirm("Clear current file before reading in new one?"))
	         == SM_NO_C)) {
	load_file_data->clear_all = FALSE;
	   if (sm_setup_getfilename("*.dat",
				    last_direct,
				    "Select file to merge: ",
				    0,
				    0)) {
	       sm_destroy_callback();
	   }
    } else if (clear_it == SM_YES_C) {
	load_file_data->clear_all = TRUE;
	if (sm_setup_getfilename("*.dat",
				 last_direct,
				 "Select file to read: ",
				 0,
				 0))
	    sm_destroy_callback();
    } else if (clear_it == SM_CANCEL_C) {
	sm_destroy_callback();
	return(-1);
    }
    return(0);
}

static void store_file ARG1(CHAR *, filename)
{
    CHAR	*buf = alloca(MAXPATHLEN + 1), *p;
    FILE	*file;

    fqn(buf, filename);
    p = strrchr(buf, '/');
    *p++ = 0;
    strcpy(last_filename, filename);
    strcpy(last_direct, buf);
    
    if (backup_file(filename) < 0) {
	sprintf(buf, "Can\'t make a backup file of \"%s\".", filename);
	sm_error(buf);
    }

    if (((file = fopen(filename, "w")) == NULL) ||
	sm_fwrite(sm_user_screens(), file)) {
	sm_file_error("WRITE FAILED to file: ", filename, errno);
    } else {
	sprintf(buf, "%s stored", filename);
	sm_message(buf);
	dirty_data = 0;
    }
    if (file)
	fclose(file);
}

CALLBACK(store_file_as)
{
    CHAR	*filename;

    /* Always entered after sm_prompt */
    if (command == SM_YES_C) {
	filename = sm_get_prompt_value();

/*
	if ((file = fopen(filename, "r")) != NULL) {
	    CHAR	*buf = alloca(100);

	    fclose(file);
	    sprintf(buf, "\"%s\" already exists. Overwrite it?", filename);
	    if (sm_confirm(buf) != SM_YES_C)
		return(SM_QUIT_C);
	}
*/
	store_file(filename);
    }

    return(command); /* To provoke reentry into main_loop */
}

static void setup_store_file_as ARG1(SM_SOFTKEY, skey)
{
    CHAR	pwd[MAXPATHLEN + 1];

    GETCWD(pwd, MAXPATHLEN);
    sm_add_callback(store_file_as, NULL);
    sm_setup_prompt("Enter filename to write (current directory is:)", 
		    pwd, 
		    last_filename,
		    skey);
}

CALLBACK(smcreate_do_exit)
{
    /* We can't just return SM_EXIT_C in case we need to exit
       without saving files */
    if (command != SM_CANCEL_C)
	while (sm_destroy_callback())
	    ; /* To kill smcreate_main_loop */
    return(SM_POPCALLBACK);
}

CALLBACK(smcreate_main_loop)
{
    alloca(0);

    sm_fld_set(sm_fld_open(main_screen, "file_name_display"),
	       last_filename);
    sm_fld_set(sm_fld_open(main_screen, "dir_display"),
	       last_direct);
    sm_fld_set(sm_fld_open(main_screen, "changed"), dirty_data? "Y": "N");

    sm_setup_scr(SM_BASE_WINDOW, main_screen, NULL, NULL, 0, 0, 0);

    switch(command) {
    case SM_LOAD_FILE_C:
	if (setup_load_file() == 0)
	    return(0);
	break;

    case SM_STORE_FILE_AS_C:
	setup_store_file_as(NULL);
	return(0);

    case SM_STORE_FILE_C:
	if (last_filename[0] == 0) {
	    setup_store_file_as(NULL);
	    return(0);
	} else
	    store_file(last_filename);
	break;

    case SM_CLEAR_FILE_C:
	if (!sm_anything_there(sm_user_screens()) ||
	    (!dirty_data ||
	     (sm_confirm("Clear current file?") == SM_YES_C))) {
	    sm_clear_out(0);
	    dirty_data = 0;
	    *last_filename = 0;
	    sm_message("File cleared");
	    sm_push_command(SM_YES_C); /* To provoke reentry */
	}
	break;

    case GLOBAL_EDITOR:
	setup_global_edt();
	return(0);

    case SKEY_EDITOR:
	setup_softkey_edt();
	return(0);

    case SCREEN_EDITOR:
	setup_screen_edt();
	return(0);

    case MENU_EDITOR:
	setup_menu_edt();
	return(0);

    case SM_EXIT_C:
	if (dirty_data) {
	    sm_add_callback(smcreate_do_exit, NULL);
	    setup_store_file_as(sm_skey_open(sm_builtin_screens(), 
					     "sm_save_exit_skey"));
	    return(0);
	} else {
	    while (sm_destroy_callback())
		; /* get rid of field callback */
	    return(command);
	}

    case SM_NO_SAVE_EXIT_C:
	while (sm_destroy_callback())
	    ; /* get rid of field callback */
	return(SM_POPCALLBACK);

    default:
	break;
    }

    return(0);
}

static void key_fmt ARG2(CHAR *, p, CHAR *, s)
{
    int		j, k;

    for (j = 7; j >= 0; j--)
	if (s[j] != ' ')
	    break;
    for (k = 0; k <= j; k++)
	*p++ = *s++;
    for (;k < 8; k++) {
	*p++ = '\\';
	*p++ = ' ';
    }
    *p = 0;
}

static void export_skey ARG2(FILE *, f, SM_SOFTKEY, skey)
{
    int		i;
    CHAR	buf[80], *p;

    for (i = 0; i < 8; i++) {
	strcpy(buf, "box \"");
	p = strchr(buf, 0);
	key_fmt(p, skey->upper[i]);
	strcat(p, "\" \"");
	p = strchr(buf, 0);
	key_fmt(p, skey->lower[i]);
	strcat(p, "\"");
	fprintf(f, "%s\n", buf);
    }
}

int main ARG2(int, argc, CHAR **, argv)
{
    int		c;
    SM_FILE	screenset;

#ifdef SM_X11 /* Really only Sun XView at the momemnt! */
    char	*disp = getenv("DISPLAY");
	
    if (disp && *disp)
	xsmio_init(argc, argv);
#endif /* SM_X11 */

    /* Temporary back door */
    if (setjmp(NoMoreHeap)) {
	alloca(0);
	sm_error_exit(sm_mem_error);
    }

    sm_pre_init(argc, argv);

    /* Now check the options - X has now removed its own ... */
    while ((c = getopt(argc, argv, "hd:k:?")) != EOF)
        switch (c) {
	case 'd':
	    global.screen_dump = optarg;
	    break;
	case 'k':
	    global.skey_dump = optarg;
	    break;
        default:  
	    /* Unfortunately, if we've entered X in sm_init, then any error 
	       messages disappear */
	    usage(); /* now do any bitching */
	    break;
        }

    if (global.screen_dump || global.skey_dump)
	sm_init(0, argc, argv);
    else
	sm_init(1, argc, argv);

    if (optind < argc) {
        if (sm_fopen(sm_user_screens(), argv[optind]) == SM_ERROR)
	    sm_error("Error reading screen file");
	strcpy(last_filename, argv[optind]);
    } else if (global.screen_dump || global.skey_dump)
	usage();
    
#ifdef SM_DEBUG
    smio_flushinp();
#endif

#if READ_DAT_AT_RUNTIME
    {
	CHAR	buf[MAXPATHLEN + 1];

	buf[0] = 0;
#if NEEDS_PATH_FOR_PROGS
	strcpy(buf, sm_program_directory);
#endif
	strcat(buf, "smcreate.dat");
	if (sm_fopen(sm_scrmgr_screens(), buf) == SM_ERROR)
	    sm_error_exit("Error reading smcreate.dat!");
    }
#else
    if (sm_dbopen(sm_scrmgr_screens(), smcreate_dat) == SM_ERROR)
	sm_error_exit("Error reading internal data block!");
#endif /* READ_DAT_AT_RUNTIME */
    
    /* Back door for when we run out of heap - horrible structure, but
       what can one do ?*/

    if (setjmp(NoMoreHeap)) {
	alloca(0);

	/* Prevent infinite loop */
	if (setjmp(NoMoreHeap)) {
	    alloca(0);
	    sm_error_exit(sm_mem_error);
	}

	sm_message(sm_mem_error);

	if (setjmp(NoMoreHeap)) {
	    /* Oh boy - really serious! */
	    alloca(0);
	    sm_error_exit("Ran out of heap - file was not saved! Sorry!");
	}

	store_file(last_filename);
	alloca(0);

	if (setjmp(NoMoreHeap) == 0)
	    sm_term();
	exit(1);
    }

    /* Open screens */
    screenset = sm_scrmgr_screens();
#include "smcreate.opn"
    
    if (global.screen_dump) {
	SM_SCREEN		screen;
	struct import_export_s 	*import_export;

	if ((screen = sm_scr_open(sm_user_screens(), global.screen_dump)) == NULL)
	    sm_error_exit("No such screen!");
	import_export = create_import_export();
	import_export->screen = screen;
	import_export->command = EXPORT_SCREEN;
	export_file(stdout, import_export);
	sm_term();
	exit(0);
    }

    if (global.skey_dump) {
	SM_SOFTKEY		skey;

	if ((skey = sm_skey_open(sm_user_screens(), global.skey_dump)) == NULL)
	    sm_error_exit("No such softkey set!");
	export_skey(stdout, skey);
	sm_term();
	exit(0);
    }

    sm_paint_scr(SM_BASE_WINDOW, main_screen, /* enhance = */ 1);
    
    sm_message(rev_string);
    sm_add_callback(smcreate_main_loop, NULL);

    if (last_filename[0] != 0)
	sm_fld_set(sm_fld_open(main_screen, "file_name_display"), 
		   last_filename);
    if (*last_direct)
	sm_fld_set(sm_fld_open(main_screen, "dir_display"),
		   last_direct);

    sm_fld_set(sm_fld_open(main_screen, "changed"), dirty_data? "Y": "N");

    sm_setup_scr(SM_BASE_WINDOW, main_screen, NULL, NULL, 0, 0, 0);
    sm_main_loop();
    sm_term();
    exit(0);
}


/* For emacs:
 * Local Variables:
 * tab-width: 8
 * End:
 */
